package com.sunday.gateway.security.configuration;

import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.sunday.authorization.security.tools.authorization.redis.operation.AuthorizationOperation;
import com.sunday.gateway.security.authentication.CustomAuthenticatedReactiveAuthorizationManager;
import com.sunday.gateway.security.authentication.CustomJwtReactiveAuthenticationManager;
import com.sunday.gateway.security.authentication.CustomRedirectServerAuthenticationSuccessHandler;
import com.sunday.gateway.security.authorization.TokenTransferFilter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationCodeAuthenticationTokenConverter;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;

import java.util.Arrays;

/**
 * security配置
 * <p>
 * 0 = {ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter@17837}
 * 1 = {HttpHeaderWriterWebFilter@17838}
 * 2 = {CsrfWebFilter@17839}
 * 3 = {ReactorContextWebFilter@17840}
 * 4 = {OAuth2AuthorizationRequestRedirectWebFilter@12878}
 * 5 = {OAuth2LoginAuthenticationWebFilter@13118}
 * 6 = {AuthenticationWebFilter@17841}
 * 7 = {LoginPageGeneratingWebFilter@17842}
 * 8 = {LogoutPageGeneratingWebFilter@17843}
 * 9 = {SecurityContextServerWebExchangeWebFilter@17844}
 * 10 = {ServerRequestCacheWebFilter@17845}
 * 11 = {LogoutWebFilter@17846}
 * 12 = {ExceptionTranslationWebFilter@17847}
 * 13 = {AuthorizationWebFilter@17848}
 *
 * @author sunday
 * @since 2024/3/15
 */
@Slf4j
@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuerUri;

    private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;

    private final StringRedisTemplate stringRedisTemplate;

//    private static final List<String> excludedAuthPages = List.of(
//            /** 图片验证码 */
//            SecurityConstants.VERIFY_CODE_PATH_URL,
//            /** 手机验证码 */
//            SecurityConstants.CAPTCHA_PATH_URL,
//            /** 令牌端点 (Token Endpoint) url */
//            SecurityConstants.OAUTH2_LOGIN_URL
//    );

    /**
     * 在Spring Security中，.oauth2Login(Customizer.withDefaults()) 和 .oauth2Client(Customizer.withDefaults()) 是两个不同的配置，分别用于处理不同的OAuth2流程。让我们分别看一下它们的用途和涉及的URL。
     * <p>
     * 1. .oauth2Login(Customizer.withDefaults())
     * 用途
     * 这个配置用于基于OAuth2的登录（Authorization Code Grant）。通常使用在用户通过第三方身份提供者（如Google, GitHub）登录你的应用。这是涉及用户的互动登录流程。
     * 涉及的URL
     * 一般情况下，这个配置会涉及到以下一些URL：
     * /oauth2/authorization/{registrationId}: 用户点击登录按钮之后，引导用户到第三方身份提供者进行身份认证的URL。
     * /login/oauth2/code/{registrationId}: 第三方身份提供者完成身份验证后，重定向回该URL，并带有授权码。
     * /oauth2/authorize 和 /oauth2/authorize/code/*: 用于处理OAuth2授权和登录结果。
     * <p>
     * 2. .oauth2Client(Customizer.withDefaults())
     * 用途
     * 这个配置用于OAuth2客户端凭证模式（Client Credentials Grant）或者用于后端与OAuth2服务提供者之间的交互，获取访问令牌，用于访问受保护的资源。这不会涉及到具体的用户互动，而是后端应用程序与服务之间的交互。
     * 涉及的URL
     * 这个配置通常涉及到如下一些URL：
     * /oauth2/token: 用于获取访问令牌。
     * /oauth2/jwks: 用于访问JSON Web Key Set，用来验证JWT。
     * 其它相关后端API URL，用于获取、刷新和使用OAuth2 token。
     * <p>
     * 总结
     * .oauth2Login(Customizer.withDefaults()) 是用于处理用户基于OAuth2登录的配置。
     * .oauth2Client(Customizer.withDefaults()) 是用于后端系统作为OAuth2客户端获取访问令牌并访问受保护资源的配置。
     * 希望这能帮助你更清楚地理解这两个配置的区别和它们各自的作用。如果你还有其他问题，随时可以问我。
     */
    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        http
                /**
                 * TODO 无用户确认请求下返回无法锁定session问题，后续跟进
                 * @see AuthenticationWebFilter#filter(ServerWebExchange, WebFilterChain)
                 * @see ServerOAuth2AuthorizationCodeAuthenticationTokenConverter#convert(ServerWebExchange)
                 * @see WebSessionOAuth2ServerAuthorizationRequestRepository#removeAuthorizationRequest(ServerWebExchange)
                 */
                .oauth2Login(oauth2Login -> oauth2Login
                                .authenticationSuccessHandler(customRedirectServerAuthenticationSuccessHandler())
//                                .authenticationSuccessHandler(jsonServerAuthenticationSuccessHandler())
//                        .authenticationFailureHandler(new JsonServerAuthenticationFailureHandler())
                )
//                .oauth2Client(Customizer.withDefaults())
                .oauth2ResourceServer(resourceServer -> resourceServer
                        .jwt(jwtSpec -> jwtSpec.authenticationManager(customJwtReactiveAuthenticationManager()))
                )
                .authorizeExchange(exchanges -> exchanges
//                        .pathMatchers(excludedAuthPages.toArray(new String[0])).permitAll()
//                                .anyExchange().authenticated()
                                .anyExchange().access(CustomAuthenticatedReactiveAuthorizationManager.authenticated())

                )
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .addFilterAfter(new TokenTransferFilter(), SecurityWebFiltersOrder.AUTHORIZATION)
//                .csrf(csrf -> csrf.disable())
//                .csrf(csrf -> csrf
//                        // 指定了一个CSRF令牌的存储方式，这里使用带有Cookies的仓库。
//                        .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
//                        .requireCsrfProtectionMatcher(exchange ->
//                                Mono.defer(() -> {
//                                    String path = exchange.getRequest().getURI().getPath();
//                                    // shouldProtect 变量用来决定一个请求是否应该被CSRF保护
//                                    boolean shouldProtect = !excludedAuthPages.stream().anyMatch(url -> url.equals(path));
//                                    return shouldProtect ? ServerWebExchangeMatcher.MatchResult.match() : ServerWebExchangeMatcher.MatchResult.notMatch();
//                                })
//                        )
//                )
        ;

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // 允许所有来源的请求
        configuration.setAllowedOrigins(Arrays.asList("*"));
        // 允许的请求方法
        configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
        // 允许的请求头
        configuration.setAllowedHeaders(Arrays.asList("*"));
        // 是否允许携带凭证（如 cookies）
//        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);

        return source;
    }

    /**
     * @return
     * @see DefaultJWTProcessor#process(SignedJWT, SecurityContext)
     * @see NimbusReactiveJwtDecoder#decode(JWT)
     * @see JwtReactiveAuthenticationManager#authenticate(Authentication)
     * @see ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec#getAuthenticationManager()
     */
    @Bean
    public ReactiveJwtDecoder jwtDecoder() {
        return ReactiveJwtDecoders.fromIssuerLocation(issuerUri);
    }

    @Bean
    public CustomJwtReactiveAuthenticationManager customJwtReactiveAuthenticationManager() {
        CustomJwtReactiveAuthenticationManager manager = new CustomJwtReactiveAuthenticationManager(jwtDecoder(), new AuthorizationOperation(stringRedisTemplate));
        manager.setJwtAuthenticationConverter(new ReactiveJwtAuthenticationConverter());
        return manager;
    }

    /**
     * 认证成功后使用的ServerAuthenticationSuccessHandler
     * 默认为 RedirectServerAuthenticationSuccessHandler重定向到“/”
     *
     * @return
     */
//    @Bean
//    public JsonServerAuthenticationSuccessHandler jsonServerAuthenticationSuccessHandler() {
//        return new JsonServerAuthenticationSuccessHandler(authorizedClientRepository);
//    }

    /**
     * 鉴权后回调地址，后续可能由前台提供
     *
     * @return
     */
    @Bean
    public CustomRedirectServerAuthenticationSuccessHandler customRedirectServerAuthenticationSuccessHandler() {
        return new CustomRedirectServerAuthenticationSuccessHandler("http://172.24.16.1/oauth2/callback", authorizedClientRepository);
    }

}